#include <QtCore/QFileInfo>
#include <QtCore/QIODevice>
#include <QtCore/QDateTime>
#include <stdio.h>
#include <string.h>
#ifdef Q_OS_UNIX
#include <signal.h>
#include <fcntl.h>
#include <pwd.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <assert.h>
#endif
#include "unixdaemon.h"

namespace smstranslator {

static UnixDaemon *daemon_singleton = 0;

UnixDaemon::UnixDaemon(const QString &config_file) :
  QObject(0),
#ifdef Q_OS_UNIX
  pid_file(-1),
#endif
  config_file_path(config_file),
  log_manager(0),
  config(0),
  kernel(0) {
  if(QMetaType::type("State") == 0)
    qRegisterMetaType<smstranslator::State>("State");
#ifdef Q_OS_UNIX
  if(daemon_singleton) {
    qFatal("Unable to construct more then one UnixDaemon instance");
  }
#endif
  daemon_singleton = this;
}

UnixDaemon::~UnixDaemon() {
  daemon_singleton = 0;
}

#ifdef Q_OS_UNIX
void UnixDaemon::signalHandler(int sig) {
  if(sig == SIGTERM) {
    daemon_singleton->kernel->stop();
  } else if(sig == SIGUSR1) {
    daemon_singleton->reloadConfig();
  }
}
#endif

void UnixDaemon::messageHandler(QtMsgType type, const char *msg) {
  if(type == QtDebugMsg) {
    daemon_singleton->log_manager->debug(UNIX_DAEMON_ID,
                                         UNIX_DAEMON_ID,
                                         UNIX_DAEMON_ID,
                                         QString(msg).toUtf8());
  } else if(type == QtWarningMsg) {
    daemon_singleton->log_manager->warning(UNIX_DAEMON_ID,
                                           UNIX_DAEMON_ID,
                                           UNIX_DAEMON_ID,
                                           QString(msg).toUtf8());
  } else if(type == QtCriticalMsg) {
    daemon_singleton->log_manager->error(UNIX_DAEMON_ID,
                                         UNIX_DAEMON_ID,
                                         UNIX_DAEMON_ID,
                                         QString(msg).toUtf8());
  } else if(type == QtFatalMsg) {
    daemon_singleton->log_manager->fatal(UNIX_DAEMON_ID,
                                         UNIX_DAEMON_ID,
                                         UNIX_DAEMON_ID,
                                         QString(msg).toUtf8());
    daemon_singleton->kernel->stop();
  }
}

void UnixDaemon::kernelStateChanged(State state) {
  switch(state) {
    case STOPED:
      destroyWorkerData();
#ifdef Q_OS_UNIX
      close(daemon_singleton->pid_file);
      remove(PID_FILE);
#endif
      QCoreApplication::exit(0);
      break;
    default:
      break;
  }
}

void UnixDaemon::createWorkerData() {
  log_manager = new log::LogManager(LOG_FILE_PATH, APP_NAME);

  qInstallMsgHandler(UnixDaemon::messageHandler);

  config = new QtJsonSettings(config_file_path);

  if(config) {
    QSettings::Status status = config->status();
    switch(status) {
      case QSettings::NoError:
        break;
      case QSettings::AccessError: {
        QString error_msg(QString("Access error to config %1")
                            .arg(config_file_path));
        daemon_singleton->log_manager->fatal(UNIX_DAEMON_ID,
                                             UNIX_DAEMON_ID,
                                             UNIX_DAEMON_ID,
                                             error_msg.toUtf8());
        destroyWorkerData();
  #ifdef Q_OS_UNIX
        close(daemon_singleton->pid_file);
        remove(PID_FILE);
  #endif
        return QCoreApplication::exit(0);
      }
      case QSettings::FormatError: {
        QString error_msg(QString("Parse error to config %1")
                            .arg(config_file_path));
        daemon_singleton->log_manager->fatal(UNIX_DAEMON_ID,
                                             UNIX_DAEMON_ID,
                                             UNIX_DAEMON_ID,
                                             error_msg.toUtf8());
        destroyWorkerData();
  #ifdef Q_OS_UNIX
        close(daemon_singleton->pid_file);
        remove(PID_FILE);
  #endif
        return QCoreApplication::exit(0);
      }
    }
  }

  kernel = new Kernel(log_manager);

  connect(kernel, SIGNAL(stateChanged(State)),
          this, SLOT(kernelStateChanged(State)));

  if(!kernel->readConfig(config)) {
    QString error_msg(QString("Read error to config (%1) error (%2)")
                        .arg(config_file_path)
                        .arg(kernel->getLastError()));
    daemon_singleton->log_manager->fatal(UNIX_DAEMON_ID,
                                         UNIX_DAEMON_ID,
                                         UNIX_DAEMON_ID,
                                         error_msg.toUtf8());
    destroyWorkerData();
#ifdef Q_OS_UNIX
    close(daemon_singleton->pid_file);
    remove(PID_FILE);
#endif
    return QCoreApplication::exit(0);
  }

  kernel->start();

  if(config) {
    delete config;
    config = 0;
  }
}

void UnixDaemon::destroyWorkerData() {
  if(kernel) {
    delete kernel;
    kernel = 0;
  }

  if(config) {
    delete config;
    config = 0;
  }

  if(log_manager) {
    delete log_manager;
    log_manager = 0;
  }
}

void UnixDaemon::daemonize() {
#ifdef Q_OS_UNIX
  int pid(fork());
  switch(pid) {
    case 0: {
#ifdef Q_OS_LINUX
      umask(027);
#endif
      setsid();
      chdir("/");
      close(STDIN_FILENO);
      close(STDOUT_FILENO);
      close(STDERR_FILENO);
      signal(SIGCHLD, SIG_IGN); /* ignore child */
      signal(SIGTSTP, SIG_IGN); /* ignore tty signals */
      signal(SIGTTOU, SIG_IGN);
      signal(SIGTTIN, SIG_IGN);
      signal(SIGHUP,  SIG_IGN); /* catch hangup signal */
      signal(SIGTERM, UnixDaemon::signalHandler); /* catch kill signal */
      signal(SIGUSR1, UnixDaemon::signalHandler); /* catch reload config signal */

      pid_file = open(PID_FILE, O_RDWR | O_CREAT, 0640);

      if(pid_file < 0) {
        qFatal("Cannot open PID file \"%s\"", PID_FILE);
      }

      if(lockf(pid_file, F_TLOCK, 0) < 0) {
        qFatal("Can't get a lock on \"%s\". "
               "Another instance is propably already running.",
               PID_FILE);
      }

      QByteArray d = QByteArray::number(getpid());
      Q_UNUSED(write(pid_file, d.constData(), d.size()));

      createWorkerData();
      break;
    }
    case -1:
      qFatal("Fail create worker process: %s\n", strerror(errno));
      break;
    default:
      exit(0);
      break;
  }
#endif
#ifdef Q_OS_WIN
  createWorkerData();
#endif
}

void UnixDaemon::reloadConfig() {
  config = new QtJsonSettings(config_file_path);

  if(config) {
    QSettings::Status status = config->status();
    switch(status) {
      case QSettings::NoError:
        break;
      case QSettings::AccessError: {
        QString error_msg(QString("Access error to config %1")
                            .arg(config_file_path));
        daemon_singleton->log_manager->error(UNIX_DAEMON_ID,
                                             UNIX_DAEMON_ID,
                                             UNIX_DAEMON_ID,
                                             error_msg.toUtf8());
        delete config;
        config = 0;
        return;
      }
      case QSettings::FormatError: {
        QString error_msg(QString("Parse error to config %1")
                            .arg(config_file_path));
        daemon_singleton->log_manager->error(UNIX_DAEMON_ID,
                                             UNIX_DAEMON_ID,
                                             UNIX_DAEMON_ID,
                                             error_msg.toUtf8());
        delete config;
        config = 0;
        return;
      }
    }
  }

  kernel->reloadConfig(config);

  config = 0;
}

} // namespace smstranslator
